﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Jaiden.Updater.WinForm
{
    public partial class FrmSplash : Form
    {
        private bool _isBusy;

        public FrmSplash()
        {
            InitializeComponent();
        }

        protected override void OnVisibleChanged(EventArgs e)
        {
            base.OnVisibleChanged(e);
            if (Visible)
            {
                Start();
            }
        }

        private async void Start()
        {
            if (_isBusy)
            {
                return;
            }

            var url = "http://i.coderbusy.com/update/updater/update.xml";
            try
            {
                _isBusy = true;
                llMessage.Text = "请求升级信息";
                using (var client = new WebClient())
                {
                    client.Encoding = Encoding.UTF8;
                    var xml = await client.DownloadStringTaskAsync(url);
                    var package = UpdatePackageHelper.Deserialize(xml);
                    if (await CheckPackage(package))
                    {
                        Process.Start("ILSpy.exe");
                        Application.Exit();
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                _isBusy = false;
            }
        }

        private async Task<Boolean> CheckPackage(UpdatePackage package)
        {
            llMessage.Text = "清理文件";
            await Task.Run(Clear);
            llMessage.Text = "检查更新";
            var needRestart = false;
            var tasks = new List<Task>();
            foreach (var filePackage in package.FilePackages)
            {
                llMessage.Text = $"验证文件：{Path.GetFileName(filePackage.FilePath)}";
                if (!await IsMatch(filePackage))
                {
                    needRestart = true;
                    var fn = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePackage.FilePath);
                    if (File.Exists(fn))
                    {
                        var del = fn + "." + DateTime.Now.Ticks + ".del";
                        File.Move(fn, del);
                    }
                    tasks.Add(DownloadFile("http://i.coderbusy.com/update/updater/", filePackage));
                }
            }
            await Task.WhenAll(tasks);
            if (needRestart)
            {
                Application.Restart();
                return false;
            }
            return true;
        }

        private void Clear()
        {
            var files = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.del", SearchOption.AllDirectories);
            if (files.Any())
            {
                foreach (var file in files)
                {
                    try
                    {
                        
                        File.Delete(file);
                    }
                    catch (Exception e)
                    {
                        //TODO:暂不处理

                    }
                }
            }
        }

        private async Task DownloadFile(string baseUrl, FilePackage package)
        {
            var fn = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Downloads");
            if (!Directory.Exists(fn))
            {
                Directory.CreateDirectory(fn);
            }
            fn = Path.Combine(fn, package.Hash + ".dat");
            if (!File.Exists(fn))
            {
                using (var client = new WebClient())
                {
                    var es = new TaskCompletionSource<Boolean>();
                    client.DownloadProgressChanged += Client_DownloadProgressChanged;
                    client.DownloadFileCompleted += (s, e) =>
                    {
                        if (e.Cancelled)
                        {
                            es.SetCanceled();
                        }
                        else if (e.Error != null)
                        {
                            es.SetException(e.Error);
                        }
                        else
                        {
                            es.SetResult(true);
                        }
                    };
                    var tmp = Path.GetTempFileName();
                    client.DownloadFileAsync(new Uri(baseUrl + package.Url), tmp);
                    if (await es.Task)
                    {
                        File.Move(tmp, fn);
                    }
                }
            }
            var dist = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, package.FilePath);
            //File.Copy(fn, dist, true);
            using (var cacheStream = File.OpenRead(fn))
            {
                using (var gzip = new GZipStream(cacheStream, CompressionMode.Decompress))
                {
                    using (var distStream = File.OpenWrite(dist))
                    {
                        await gzip.CopyToAsync(distStream);
                    }
                }
            }
        }

        private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            //TODO:
        }

        private async Task<bool> IsMatch(FilePackage file)
        {
            var fn = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, file.FilePath);
            if (!File.Exists(fn))
            {
                return false;
            }

            using (var fs = File.OpenRead(fn))
            {
                if (fs.Length != file.FileSize)
                {
                    return false;
                }

                var hash = await Task.Run(() => Hash(fs));
                if (hash != file.FileHash)
                {
                    return false;
                }
            }

            return true;
        }

        private String Hash(Stream stream)
        {
            using (var md5 = new MD5CryptoServiceProvider())
            {
                var bytes = md5.ComputeHash(stream);
                var hash = BitConverter.ToString(bytes).Replace("-", string.Empty);
                return hash;
            }
        }
    }
}